#!/usr/bin/env bash

if ${__include_sh__:-false}; then
    return
else
    __include_sh__=true
fi

declare -A include_maps
include_maps["include.sh"]=0
include_research_paths=($(dirname ${BASH_SOURCE[0]}))
include_counter=1

if ${SHELL_DEBUG:-false} && ! ${SHELL_PREF:-false}; then
function include()
{
    local found=false

    include::push_setting errexit
    include::enable_setting errexit
    for path in ${include_research_paths[*]}; do
        local pathfile="$path/$1"
        if [ ! -e "$pathfile" ]; then
            continue
        fi

        found=true
        if [ "${include_maps[$pathfile]}" == "" ]; then
            include_maps[$pathfile]="$((include_counter++))"
            Trace::Value include_counter "$include_counter"
            Trace::StartTrace "include $pathfile"
            source $pathfile
            Trace::EndTrace
        fi
    done
    include::pop_setting

    assert "$found" == "true" "cannot include $1"
}
else
function include()
{
    for path in ${include_research_paths[*]}; do
        local pathfile="$path/$1"
        if [ "${include_maps[$pathfile]}" != "" ]; then
            continue
        fi

        if [ ! -e "$pathfile" ]; then
            continue
        fi

        include_maps[$pathfile]="$((include_counter++))"
        Trace::Value include_counter "$include_counter"
        Trace::StartTrace "include $pathfile"
        source $pathfile
        Trace::EndTrace
    done
}
fi

if ${SHELL_PREF:-false}; then
    TRACE_FILE="${TRACE_FILE:-$HOME/analyzing.trace}"
    TRACE_Operations=()
    TRACE_Contents=()
    TRACE_Times=()

    function Trace::StartTrace()
    {
        TRACE_Operations+=("B")
        TRACE_Contents+=("${1// /_}")
        TRACE_Times+=("$EPOCHREALTIME")
    }

    function Trace::EndTrace()
    {
        TRACE_Operations+=("E")
        TRACE_Contents+=("-")
        TRACE_Times+=("$EPOCHREALTIME")
    }

    function Trace::ScopeTrace()
    {
        Trace::StartTrace "$@"
        slient Scope::AddDestroyCommandForUpper 2 'Trace::EndTrace'
    }

    function Trace::Value()
    {
        TRACE_Operations+=("C")
        TRACE_Contents+=("${1// /_}|${2// /_}")
        TRACE_Times+=("$EPOCHREALTIME")
    }

    function Trace::GenerateTrace()
    {
        for ((i = 0; i < ${#TRACE_Operations[*]}; i++)); do
            echo "bash-$$ ($$) [000] .... ${TRACE_Times[$i]}: tracing_mark_write: ${TRACE_Operations[$i]}|$$|${TRACE_Contents[$i]}" >> "$TRACE_FILE"
        done
    }

    trap 'Trace::GenerateTrace' EXIT
else
    function Trace::StartTrace()
    {
        return
    }

    function Trace::EndTrace()
    {
        return
    }

    function Trace::ScopeTrace()
    {
        return
    }

    function Trace::Value()
    {
        return
    }
fi

function include::research_path_add()
{
    local path="$(realpath $1)"
    if [ -e "$path" ]; then
        include_research_paths+=("${path}")
    fi
}

function include::is_setting()
{
    if ! result=$(set -o | egrep "^$1 "); then
        assert_fail "is_setting: no such setting: $1"
        exit 1
    fi

    [[ "$result" =~ (on$) ]]
}

function include::enable_setting()
{
    set -o $1
}

function include::disable_setting()
{
    set +o $1
}

include_setting_stack=()
function include::push_setting()
{
    local setting
    if include::is_setting $1; then
        setting="include::enable_setting $1"
    else
        setting="include::disable_setting $1"
    fi

    include_setting_stack[${#include_setting_stack[*]}]="$setting"
}

function include::pop_setting()
{
    local ret=$?
    ${include_setting_stack[$((${#include_setting_stack[*]} - 1))]}
    unset include_setting_stack[$((${#include_setting_stack[*]} - 1))]
    return $ret
}

function define_empty_function()
{
    eval "$1() { return 0; }"
}

define_empty_function log_depth_inc
define_empty_function log_depth_dec
define_empty_function dumpstack

function loge()
{
    echo "$1"
}

function assert()
{
    log_depth_inc
    if ! [ "$1" "$2" "$3" ]; then
        assert_fail "$4"
    fi
    log_depth_dec
}

function assert_fail()
{
    log_depth_inc
    loge "assert_fail: $1" 2>/dev/null
    log_depth_dec
    ${SHELL_DEBUG:-false} && dumpstack
    exit 3
}

function call()
{
    if [ "$1" == "" ]; then
        return
    fi

    if ! declare -f $1 1>/dev/null; then
        return 1
    fi
    "$@"
}

function slient()
{
    "$@" 1>/dev/null 2>&1
}

function IsProcExist()
{
    slient ps -p "$1"
}

DEBUGLOG=${DEBUGLOG:-false}
function debuglog()
{
    $DEBUGLOG || return
    local now="$(date +%N)"
    printf "%d %d.%s %s:%3d " \
        "$BASHPID" \
        "$(date +%s)" \
        "${now:0:3}" \
        "${BASH_SOURCE[$((1 + LOG_DEPTH))]}" \
        "${BASH_LINENO[$LOG_DEPTH]}"
    echo -en "\033[36m$*\033[0m\n"
}
